// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包singleflight提供了重复函数调用抑制
// 机制。
package singleflight

import "sync"

// 调用是正在进行的或已完成的单次飞行。Do call 
type call struct {
	wg sync.WaitGroup

	// 这些字段在WaitGroup完成之前写入一次
	// 并且仅在WaitGroup完成后读取。
	val interface{}
	err error

	// 这些字段在WaitGroup完成之前使用singleflight 
	// 互斥锁读取和写入，在WaitGroup完成之后读取但不写入。
	dups  int
	chans []chan<- Result
}

// 组表示一个工作类，并在
// 中形成一个命名空间，可以使用重复抑制来执行哪些工作单元。
type Group struct {
	mu sync.Mutex       // 保护m 
	m  map[string]*call // 延迟初始化
}

// 结果保存Do的结果，因此可以在通道上传递这些结果。
type Result struct {
	Val    interface{}
	Err    error
	Shared bool
}

// Do执行并返回给定函数的结果，使
// 确保在一次
// 中只有一次执行给定密钥。如果出现重复，重复调用方将等待
// 原件完成并接收相同的结果。
// 返回值shared表示是否向多个调用者提供了v。
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
	g.mu.Lock()
	if g.m == nil {
		g.m = make(map[string]*call)
	}
	if c, ok := g.m[key]; ok {
		c.dups++
		g.mu.Unlock()
		c.wg.Wait()
		return c.val, c.err, true
	}
	c := new(call)
	c.wg.Add(1)
	g.m[key] = c
	g.mu.Unlock()

	g.doCall(c, key, fn)
	return c.val, c.err, c.dups > 0
}

// DoChan与Do类似，但返回一个通道，该通道将在准备就绪时接收
// 结果。如果函数
// 最终将被调用，则第二个结果为true；如果不被调用，则为false（因为有
// 一个使用此键的挂起请求）。
func (g *Group) DoChan(key string, fn func() (interface{}, error)) (<-chan Result, bool) {
	ch := make(chan Result, 1)
	g.mu.Lock()
	if g.m == nil {
		g.m = make(map[string]*call)
	}
	if c, ok := g.m[key]; ok {
		c.dups++
		c.chans = append(c.chans, ch)
		g.mu.Unlock()
		return ch, false
	}
	c := &call{chans: []chan<- Result{ch}}
	c.wg.Add(1)
	g.m[key] = c
	g.mu.Unlock()

	go g.doCall(c, key, fn)

	return ch, true
}

// doCall处理对密钥的单个调用。
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
	c.val, c.err = fn()
	c.wg.Done()

	g.mu.Lock()
	delete(g.m, key)
	for _, ch := range c.chans {
		ch <- Result{c.val, c.err, c.dups > 0}
	}
	g.mu.Unlock()
}

// ForgetUnshared告诉singleflight忘记一个密钥，如果它不是与任何其他goroutine共享的。将来调用忘记的键
// 将调用该函数，而不是等待前面的调用完成。
// 返回密钥是否被遗忘或未知——也就是说，是否没有
// 其他goroutine正在等待结果。
func (g *Group) ForgetUnshared(key string) bool {
	g.mu.Lock()
	defer g.mu.Unlock()
	c, ok := g.m[key]
	if !ok {
		return true
	}
	if c.dups == 0 {
		delete(g.m, key)
		return true
	}
	return false
}
